package com.maikang.exammall.service;

import com.maikang.exammall.entity.Commission;
import com.maikang.exammall.entity.dto.order.OrderWithUserDTO;
import com.maikang.exammall.entity.dto.promote.EarningDTO;
import com.maikang.exammall.entity.dto.promote.EarningDetailDTO;
import com.maikang.exammall.entity.dto.promote.StatusAmountOnly;
import com.maikang.exammall.repository.CommissionRepository;
import com.maikang.exammall.repository.OrderRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.domain.SliceImpl;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ObjectUtils;

import java.util.*;

import static com.maikang.exammall.entity.constant.OrderStatus.*;


@Log4j2
@Service
@RequiredArgsConstructor
public class CommissionService extends BaseService<Commission, Long, CommissionRepository> {

    private final OrderRepository orderRepository;

    @Transactional(readOnly = true)
    public EarningDTO findEarningBySaleId(Long saleId) {
        /**
         * accumulated - withdrawing - withdrawn = withdrawable;
         */
        Long accumulated = 0L;
        Long withdrawing = 0L;
        Long withdrawn = 0L;

        List<StatusAmountOnly> commissions = repository.findBySaleIdAndStatusBetween(saleId, EXAMINED_TO_COMMENT, WITHDRAWN);
        if (ObjectUtils.isEmpty(commissions)) return new EarningDTO(0L, 0L, 0L, 0L);
        for (StatusAmountOnly commission : commissions) {
            Integer status = commission.getStatus();
            if (status.equals(EXAMINED_TO_COMMENT) || status.equals(COMMENTED)) {
                accumulated += commission.getAmount();
            } else if (status.equals(WITHDRAWING)) {
                withdrawing += commission.getAmount();
            } else if (status.equals(WITHDRAWN)) {
                withdrawn += commission.getAmount();
            } else {
                throw new RuntimeException("got unexpected status " + status);
            }
        }
        Long withdrawable = accumulated - withdrawing - withdrawn;
        return EarningDTO.builder().accumulated(accumulated)
                .withdrawable(withdrawable)
                .withdrawn(withdrawn)
                .withdrawing(withdrawing).build();
    }


    @Transactional(readOnly = true)
    public SliceImpl<EarningDetailDTO> findCommissionList(Long saleId, Date fromDate, Date toDate, Pageable pageable) {
        Slice<Commission> commissionsSlice = repository.findBySaleIdAndStatusBetweenAndCreatedDateBetween(saleId, TO_PAY, COMMENTED, fromDate, toDate, pageable);
        List<Commission> commissions = commissionsSlice.getContent();
        if (ObjectUtils.isEmpty(commissions)) return null;

        Map<Long, List<Commission>> orderIdCommissionListMap = new HashMap<>();
        commissions.forEach(commission -> orderIdCommissionListMap.computeIfAbsent(commission.getOrderId(), key -> new ArrayList<>()).add(commission));
        Set<Long> orderIds = orderIdCommissionListMap.keySet();

        Map<Long, OrderWithUserDTO> orderIdOrderMap = new HashMap<>();
        List<OrderWithUserDTO> orderWithUserList = orderRepository.findOrderWithUserByIds(orderIds);
        orderWithUserList.forEach(orderWithUserDTO -> orderIdOrderMap.put(orderWithUserDTO.getId(), orderWithUserDTO));

        List<EarningDetailDTO> ret = new ArrayList<>();
        for (Map.Entry<Long, List<Commission>> entry : orderIdCommissionListMap.entrySet()) {
            Long orderId = entry.getKey();
            List<Commission> commissions1 = entry.getValue();

            int commissionOrderStatus;
            Long totalEarning = 0L;

            Set<Long> refundOrderItemIds = new HashSet<>();
            Set<Long> paidOrderItemIds = new HashSet<>();
            Set<Long> examinedOrCommentedOrderItemIds = new HashSet<>();

            for (Commission commission : commissions1) {// 2, 6, 7 +, 3, 4, 5 = 0,
                Integer status = commission.getStatus();
                if (status.equals(PAID) || status.equals(EXAMINED_TO_COMMENT) || status.equals(COMMENTED)) {
                    totalEarning += commission.getAmount();
                }
                if (status.equals(REFUNDED) || status.equals(APPLYING_FOR_REFUND)) {
                    refundOrderItemIds.add(commission.getOrderItemId());
                } else if (status.equals(PAID)) {
                    paidOrderItemIds.add(commission.getOrderItemId());
                } else if (status.equals(EXAMINED_TO_COMMENT) || status.equals(COMMENTED)) {
                    examinedOrCommentedOrderItemIds.add(commission.getOrderItemId());
                }
            }
            if (refundOrderItemIds.size() == commissions1.size()) {
                commissionOrderStatus = 3; // 已退款
            } else if (refundOrderItemIds.size() + examinedOrCommentedOrderItemIds.size() == commissions1.size()) {
                commissionOrderStatus = 2; // 已完成
            } else if (paidOrderItemIds.size() != 0) {
                commissionOrderStatus = 1; // 待完成
            } else {
                commissionOrderStatus = 0; // 其他
            }

            ret.add(EarningDetailDTO.builder().amount(totalEarning)
                    .clientId(orderIdOrderMap.get(orderId).getUserId())
                    .clientAvatarUrl(orderIdOrderMap.get(orderId).getAvatarUrl())
                    .clientUsername(orderIdOrderMap.get(orderId).getUsername())
                    .amount(totalEarning)
                    .outTradeNo(orderIdOrderMap.get(orderId).getOutTradeNo())
                    .orderId(orderId)
                    .commissionOrderStatus(commissionOrderStatus)
                    .orderCreatedDate(orderIdOrderMap.get(orderId).getCreatedDate())
                    .commissions(commissions1).build());
        }

        ret.sort((o1, o2) -> {
            if (o1.getOrderCreatedDate().before(o2.getOrderCreatedDate())) {
                return 1;
            }
            if (o1.getOrderCreatedDate().equals(o2.getOrderCreatedDate())) {
                return 0;
            }
            return -1;
        });
        return new SliceImpl<>(ret, pageable, commissionsSlice.hasNext());
    }
}
